﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JaernCloud.Language.Compiler.Types;
using System.Linq.Expressions;
using System.Dynamic;
using JaernCloud.Language.Compiler.Ast;
using Microsoft.CSharp.RuntimeBinder;
using CSharpBinder = Microsoft.CSharp.RuntimeBinder.Binder;

namespace JaernCloud.Language.Runtime {
    
    /// <summary>
    /// Represents an expression.
    /// </summary>
    public class ExpressionNodeObject : NodeObject<IExpressionNodeType>, IExpressionNodeObject {

        Expression _expressionValue;
        Func<dynamic> _expressionLambda;

        public override string ToString() {
            return Type.Expression.ToString();
        }

        public override INodeObject<IExpressionNodeType> FromType(INodeType nodeType, INodeObjectGenerator objectGenerator) {
            return new ExpressionNodeObject { _type = (IExpressionNodeType)nodeType, _objectGenerator = objectGenerator };
        }

        public dynamic ExpressionValue {
            get {
                if (_expressionValue == null) {
                    if (Type.Expression is AstPropertyExpression) {
                        AstPropertyExpression propertyExpression = Type.Expression as AstPropertyExpression;
                        INodeObject<INodeType> owner = null;
                        // TODO: Decide on the rules behind expression that match (node).property1.property2 and (node.classnameisproperty1).property2!
                        foreach (String propertyName in propertyExpression.PropertyNames) {
                            if (owner == null) {
                                // find the owner in the current object scope
                                var parent = this.Parent;
                                while (parent != null && owner == null) {
                                    if (parent is IQueryNodeObject) {
                                        owner = (parent as IQueryNodeObject).EvaluateExpression(propertyName, this);
                                    } else {
                                        foreach (var child in parent.Children) {
                                            if (child.Names.Contains(propertyName)) {
                                                owner = child;
                                                break;
                                            }
                                        }
                                    }
                                    if (owner == null) {
                                        parent = parent.Parent;
                                    }
                                }
                                if (owner == null) {

                                    // no node instance found for the expression (happens in ajax request where a partial instance tree is used)
                                    // so, locate the owner using the type instead:
                                    NodeIdentifierScope scope = new NodeIdentifierScope(Type);
                                    var expressionType = scope.Lookup(AstPropertyExpression.FromIdent(propertyName));
                                    if (expressionType != null) {
                                        var instance = _objectGenerator.GetNodeObjectInstanceFor(expressionType);
                                        owner = instance;
                                    }

                                    if (owner == null) {
                                        throw new UnknownIdentifierException(propertyName, Type.SourceNode);
                                    }
                                }
                                _expressionValue = Expression.Constant(owner);
                            } else {
                                // get members on existing expression value (CSharpBinder has the same semantics as (dynamic owner; owner.propertyName etc.)
                                var arguments = new List<CSharpArgumentInfo>(1) { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) };
                                var binder = CSharpBinder.GetMember(CSharpBinderFlags.InvokeSimpleName, propertyName, owner.GetType(), arguments);
                                _expressionValue = Expression.Dynamic(binder, typeof(object), _expressionValue);
                            }
                        }

                        if (propertyExpression.Operator.HasValue) {
                            // TODO: Look at expression type and convert to the right number type (int, long, float, double etc.)
                            switch (propertyExpression.Operator.Value) {
                                case AstOperator.Increment: {
                                        var arguments = new List<CSharpArgumentInfo>(1) { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) };
                                        var binder = CSharpBinder.UnaryOperation(CSharpBinderFlags.InvokeSimpleName, ExpressionType.Increment, typeof(int), arguments);
                                        _expressionValue = Expression.Dynamic(binder, typeof(object), _expressionValue);
                                    }
                                    break;
                                case AstOperator.Decrement: {
                                        var arguments = new List<CSharpArgumentInfo>(1) { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) };
                                        var binder = CSharpBinder.UnaryOperation(CSharpBinderFlags.InvokeSimpleName, ExpressionType.Decrement, typeof(int), arguments);
                                        _expressionValue = Expression.Dynamic(binder, typeof(object), _expressionValue);
                                    }
                                    break;
                                case AstOperator.Add:
                                case AstOperator.Subtract:
                                case AstOperator.Divide:
                                case AstOperator.Times:
                                default:
                                    throw new NotSupportedException(propertyExpression.Operator.Value + "is not yet supported");
                            }
                            _expressionValue = RuntimeUtils.EnsureObjectResult(_expressionValue);
                        }

                        // create block to return the value
                        _expressionValue = Expression.Block(_expressionValue);
                        // compile to delegate
                        _expressionLambda = Expression.Lambda<Func<dynamic>>(_expressionValue).Compile();
                    } else if (Type.Expression is AstBooleanExpression) {
                        throw new NotSupportedException("Boolean expression is not yet supported in JaernCloud.");
                    }
                }
                return _expressionLambda();
            }
        }
    }
}
